package cz.cuni.amis.pogamut.episodic.schemas;

import cz.cuni.amis.pogamut.episodic.decisions.Node;
import java.io.Serializable;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map.Entry;
import org.apache.commons.collections15.multimap.MultiHashMap;

/**
 * The class <code>SchemaEpisodeNode</code> is a mirror of <code>Node</code>s
 * from decision tree and <code>EpisodeNode</code>s from episode tree in
 * the schemas. Each schema episode node is associated with exactly one node
 * in decision trees, so for each node in episode trees it is possible to find
 * its associated schema node. Same of their equivalents, schema episode nodes
 * can have schema affordance slots attached to them.
 * <p>
 * Together with <code>SlotContents</code> (linkage between given object and
 * a affordanceslot in schemas) they form a set that is indexing different
 * schema counters. They keep list of references to counters they contribute
 * to. This list can be quite long, but it always contains at least a single
 * counter for current schema episode node.
 *
 * @author Michal Cermak
 */
public class SchemaEpisodeNode implements Serializable {
    /**
     * Determines if a de-serialized file is compatible with this class.
     *
     * Maintainers must change this value if and only if the new version
     * of this class is not compatible with old versions. See Sun docs
     * for <a href=http://java.sun.com/products/jdk/1.1/docs/guide
     * /serialization/spec/version.doc.html> details. </a>
     *
     * Not necessary to include in first version of the class, but
     * included here as a reminder of its importance.
     */
    private static final long serialVersionUID = 1L;

    /**
     * ID of this node. Used as an ID of vertex representing
     * this chronobag when visualizing Schemas.
     */
    final int id;

    /**
     * Name of the intention, action or atomic action that is
     * represented by this schema node. Same as the name of
     * an associated node.
     */
    final String name;

    /**
     * Reference to the associated decision node. It can be either
     * action, intention (goal) or atomic action. Each schema nodes
     * is associated with exactly one decision node.
     */
    private final Node associatedNode;

    /**
     * Same as actions and intetions in decision tree, their schema equivalents
     * can have affordance slots attached to them. These slots are kept
     * in the <code>slots</code> list. Each slot is equivalent with exactly
     * one affordance slot in decision tree. Through schema slots it is
     * possible to find out how many times was each item used to satisfy
     * the given slot.
     */
    private HashMap<String, SchemaSlot> slots = new HashMap<String, SchemaSlot>();

    /**
     * <code>counts</code> is a collection of all <code>SchemaCounter</code>s
     * this node contributes to. In order to quickly access any counter,
     * counters are indexed in a multihashmap. <strong>Index of a counter is calculated
     * as the sum of IDs of all nodes it refers to</strong>, but when accessing
     * a counter from the inside of the node, nodes id is not added to the sum.
     * So a single counters of nodes can always be accessed on zero index.
     */
    public TemporalSchema counts = new TemporalSchema();

    /**
     * Instantiate the class by providing its name, reference to associated
     * decision <code>Node</code>, and its unique ID.
     * <p>
     * Mutual association with associated node is created in constructor.
     *
     * @param   _associatedNode Reference to associated <code>Node</code>.
     * @param   _id Unique ID of the new node.
     * @param   _name   Name of the node.
     */
    public SchemaEpisodeNode(Node _associatedNode, int _id, String _name) {
        associatedNode = _associatedNode;
        id = _id;
        name = _name;

        associatedNode.setAssociatedSchemaNode(this);
    }

    /**
     * Getter method for the <code>name</code> variable.
     *
     * @return  Returns the name of the intention, action or atomic action
     * that is represented by this schema node. Same as the name of
     * an associated node.
     */
    public String getName() {
        return name;
    }

    /**
     * Getter method for the <code>name</code> variable.
     *
     * @return  Returns the ID of this node. Used as an ID of vertex representing
     * this chronobag when visualizing Schemas.
     */
    public int getId() {
        return id;
    }

    /**
     * Getter method for the <code>name</code> variable.
     *
     * @return  Returns the reference to the associated decision node.
     * It can be either action, intention (goal) or atomic action.
     * Each schema nodes is associated with exactly one decision node.
     */
    public Node getAssociatedNode() {
        return associatedNode;
    }

    /**
     * This method attaches a new slot to this <code>SchemaEpisodeNode</code>.
     * There should be no two slots of same type attached to one node,
     * but since this should already hold for the associated node, it should
     * never happen for the schema node either.
     *
     * @param s     Type of the slot and class of item that satisfies it.
     * @param id    Unique ID used for visualizing purposes.
     * @return  Returns true if the new slot was attached to the node. Returns
     * false if slot of specified type already existed under this node.
     */
    public boolean addSlot(String s, int id) {
        if (slots.containsKey(s)) return false;
        slots.put(s, new SchemaSlot(id, s));
        return true;
    }

    /**
     * This method can be used to retrieve one specified slot attached to
     * this node.
     *
     * @param s Type of the slot that is to be returned.
     * @return  Returns the <code>SchemaSlot</code> of specified type
     * that was attached to this node. If no such slot exists returns
     * <code>null</code>.
     */
    public SchemaSlot getSlot(String s) {
        return slots.get(s);
    }

    /**
     * Basically a getter method for the <code>slots</code> variable.
     *
     * @return  Returns a collection of all <code>SchemaSlot</code>s
     * attached to this node. Each slot is equivalent with exactly
     * one affordance slot in decision tree. Through schema slots it is
     * possible to find out how many times was each item used to satisfy
     * the given slot.
     */
    public Collection<SchemaSlot> getSlots() {
        return slots.values();
    }

    /**
     * Each <code>SchemaEpisodeNode</code> keeps the list of references
     * to counters it is contributing to (it is contained in set of nodes
     * defining the counter). So when a new counter is created, that
     * is co-defined by this node, it need to be added to this list.
     * <p>
     * This method adds a speficied counter to this list under a specified
     * key.
     * @param key   Index of the new <code>SchemaCounter</code>. It is the key
     * of the counter minus the ID of current node.
     * @param count Reference to the <code>SchemaCounter</code> that should
     * be added to the list (actually a map) of counters.
     */
    public void addCount(int level, int index, int key, SchemaCounter count) {
        counts.getSchema(level, index).put(key, count);
    }

    /**
     * Returns actual count of this single schema episode node. Can be used to
     * set count at creation for episode nodes.
     *
     * @return  Returns an integer number representing current number of
     * executions of this node.
     */
    public Integer getSingleCount(int level, int index) {
        // In this case, we're only interested in the global counts.
        assert (!counts.getSchema(level, index).get(0).isEmpty());
        return counts.getSchema(level, index).get(0).iterator().next().getCount();
    }

    public MultiHashMap<Integer, SchemaCounter> getCounts() {
        return counts.getSchema();
    }

    public MultiHashMap<Integer, SchemaCounter> getCounts(int level, int index) {
        return counts.getSchema(level, index);
    }

    // Returns counts as iterable HashSet
    public HashSet<SchemaCounter> getCountsSet(MultiHashMap<Integer, SchemaCounter> counter) {
        HashSet<SchemaCounter> rv = new HashSet<SchemaCounter>();
        for (Entry<Integer, Collection<SchemaCounter>> e : counter.entrySet()) {
            rv.addAll(e.getValue());
        }
        return rv;
    }

    public SchemaEpisodeNode getParentSchemaNode() {
        if (associatedNode.parent != null) {
            return associatedNode.parent.getAssociatedNode();
        } else {
            return null;
        }
    }

    public Collection<SchemaEpisodeNode> getChildrenSchemaNodes() {
        Collection<SchemaEpisodeNode> col = new HashSet<SchemaEpisodeNode>();
        for (Node n : associatedNode.getAllChildrenNodes()) {
            if (n.getAssociatedNode() != null) {
                col.add(n.getAssociatedNode());
            }
        }
        return col;
    }
}
